package oboe.core;

import flash.utils.Dictionary;
import flash.utils.describeType;
import flash.utils.getQualifiedClassName;

class SigSlotCollection
{
private var children:Dictionary;

private var debugTraces:Bool;

public static inline var globalSigIndex:Dictionary = new Dictionary();
private static var globalSlotIndex:Dictionary = new Dictionary();
private static var globalPipeIndex:Dictionary = new Dictionary();
private var slotNames:Dictionary;
private var slotRegistry:Dictionary;

public function new( weakKeys:Bool=false )
{	
	this.debugTraces = Oboe.flags["DEBUG"];
	this.children = new Dictionary(weakKeys);
	if( !(Object(this).constructor in globalSigIndex ) ) globalSigIndex[ Object(this).constructor ] = {}; 
}

private function _getSigSlotChildren():Dictionary
{
	return this.children;
}

private function _getFinalClass():Class
{
	return Object(this).constructor;
}

private static function setupSlots( child:SigSlotCollection ):Void
{
	var c:Class = child._getFinalClass();
	var slots:Dictionary;
	var pipes:Dictionary;

	var checkPipes:Bool = child is SigSlot;
	pipes = globalPipeIndex[ c ];
	if( !(slots=globalSlotIndex[ c ]) )
	{
	slots = new Dictionary();
	globalSlotIndex[ c ] = slots;
	var xml:XMLList = describeType( c )..method;
	var len:Int = xml.length();
	if( checkPipes )
	{
		pipes = new Dictionary();
		globalPipeIndex[ c ] = pipes;
	}
	for( var i:Int=0; i<len; ++i )
	{
		var name:String = String(xml.@name[i]);
		if( name.substr(0, 5) == "slot_" )
		{
		name = name.substr( 5 );
		slots[ name ] = name;
		}
		if( checkPipes && name.substr(0, 5) == "pipe_" )
		{
		name = name.substr( 5 );
		pipes[ name ] = name;
		}
	}
	}
	child.slotNames = slots;
	child.message("_onInitPipes", pipes);
}

private function addChild( child:SigSlotCollection ):Void
{
	if( !this.slotNames )
	{
	SigSlotCollection.setupSlots( this );
	}
	
	if( !child || child == this ) return;
	if( child && child.debugTraces ) trace( "adding child", getQualifiedClassName(child), " to ", getQualifiedClassName(this) );
	
	if( !child.slotNames )
	{
	SigSlotCollection.setupSlots( child );
	}
	
	if( !this.slotRegistry )
	{
	this.slotRegistry = new Dictionary( false );
	}
	
	for each( var slotName:String in child.slotNames )
	{
	var slotList:Dictionary = this.slotRegistry[ slotName ];
	if( !slotList )
	{
		slotList = new Dictionary( true );
		this.slotRegistry[ slotName ] = slotList;
	}
	slotList[ child ] = child;
	}
	
	this.children[ child ] = child;
}

private function removeChild( child:SigSlotCollection ):Void
{
	if( !this.children[ child ] ) return;
	if( child.debugTraces ) trace( "removing child", getQualifiedClassName(child), " from ", getQualifiedClassName(this) );
	delete this.children[ child ];
	
	for each( var slotName:String in child.slotNames )
	{
	var slotList:Dictionary = this.slotRegistry[ slotName ];
	if( !slotList )
	{
		continue;
	}
	delete slotList[ child ];
	}
}

public function message( name:String, ... arguments ):Void
{
	var slotName:String = "slot_" + name;
	
	if( !this.slotNames )
	{
	SigSlotCollection.setupSlots( this );
	}
	
	if( this.slotNames[ name ] )
	{
	this[ slotName ].apply( this, arguments );
	}
}

public function messageEx( name:String, arguments:Array ):Void
{
	var slotName:String = "slot_" + name;
	
	if( !this.slotNames )
	{
	SigSlotCollection.setupSlots( this );
	}
	
	if( this.slotNames[ name ] )
	{
	this[ slotName ].apply( this, arguments );
	}
}	

private function signalDownEx( name:String, arguments:Array ):Void
{
	if( this.slotRegistry )
	{
	var listeners:Dictionary = this.slotRegistry[ name ];
	var slot:SigSlotCollection;
	if( !listeners ) return;
	
	for each( slot in listeners )
	{
		slot.messageEx( name, arguments );
	}
	}		
}


private function signalDown( name:String, ... arguments ):Void
{
	if( this.slotRegistry )
	{
	var listeners:Dictionary = this.slotRegistry[ name ];
	var slot:SigSlotCollection;
	if( !listeners ) return;
	
	for each( slot in listeners )
	{
		slot.messageEx( name, arguments );
	}
	}		
}

private function getSlots():Dictionary
{
	return this.slotNames;
}
}